17.5 Skills 测试与调试

17 分钟阅读

测试与调试概述#

测试和调试是开发高质量 Skills 的关键环节。本节将详细介绍 Skills 的测试方法、调试技巧和最佳实践。

测试策略#

1. 单元测试#

1.1 基本测试

python
# tests/test_text_processor.py import pytest from skills.text_processor import TextProcessorSkill from claude_code_sdk import SkillContext class TestTextProcessorSkill: """文本处理 Skill 测试""" def setup_method(self): """设置方法""" self.skill = TextProcessorSkill() self.context = SkillContext() def test_skill_initialization(self): """测试 Skill 初始化""" assert self.skill.name == "text-processor" assert self.skill.version == "1.0.0" assert self.skill.description == "Process and transform text" def test_get_parameters_schema(self): """测试获取参数模式""" schema = self.skill.get_parameters_schema() assert "properties" in schema assert "text" in schema["properties"] assert "operations" in schema["properties"] assert "required" in schema assert "text" in schema["required"] def test_execute_uppercase(self): """测试大写转换""" result = self.skill.execute( { "text": "hello world", "operations": [{"type": "uppercase"}] }, self.context ) assert result.success assert result.data["processed"] == "HELLO WORLD" def test_execute_lowercase(self): """测试小写转换""" result = self.skill.execute( { "text": "HELLO WORLD", "operations": [{"type": "lowercase"}] }, self.context ) assert result.success assert result.data["processed"] == "hello world" def test_execute_multiple_operations(self): """测试多个操作""" result = self.skill.execute( { "text": "Hello World", "operations": [ {"type": "lowercase"}, {"type": "remove_spaces"} ] }, self.context ) assert result.success assert result.data["processed"] == "helloworld" def test_execute_missing_text(self): """测试缺少必需参数""" result = self.skill.execute( {"operations": [{"type": "uppercase"}]}, self.context ) assert not result.success assert "error" in result.data def test_execute_invalid_operation(self): """测试无效操作""" result = self.skill.execute( { "text": "hello", "operations": [{"type": "invalid"}] }, self.context ) assert not result.success assert "error" in result.data

1.2 使用固件

python
# tests/conftest.py import pytest from claude_code_sdk import SkillContext from skills.text_processor import TextProcessorSkill @pytest.fixture def skill(): """Skill 固件""" return TextProcessorSkill() @pytest.fixture def context(): """上下文固件""" return SkillContext() @pytest.fixture def sample_text(): """示例文本固件""" return "Hello World" @pytest.fixture def sample_operations(): """示例操作固件""" return [ {"type": "uppercase"} ] @pytest.fixture def mock_context(mocker): """模拟上下文固件""" context = mocker.Mock(spec=SkillContext) return context

1.3 参数化测试

python
# tests/test_text_processor_parametrized.py import pytest from skills.text_processor import TextProcessorSkill from claude_code_sdk import SkillContext class TestTextProcessorParametrized: """参数化测试""" @pytest.mark.parametrize("input_text,operation,expected_output", [ ("hello", "uppercase", "HELLO"), ("HELLO", "lowercase", "hello"), ("hello", "title", "Hello"), ("hello", "reverse", "olleh"), ("hello world", "remove_spaces", "helloworld") ]) def test_operations(self, input_text, operation, expected_output): """参数化操作测试""" skill = TextProcessorSkill() context = SkillContext() result = skill.execute( { "text": input_text, "operations": [{"type": operation}] }, context ) assert result.success assert result.data["processed"] == expected_output
bash
### 2. 集成测试
#### 2.1 文件系统集成测试

python

tests/test_file_analyzer_integration.py

import pytest import os import tempfile from skills.file_analyzer import FileAnalyzerSkill from claude_code_sdk import SkillContext

class TestFileAnalyzerIntegration: """文件分析 Skill 集成测试"""

bash
def setup_method(self):
    """设置方法"""
    self.skill = FileAnalyzerSkill()
    self.context = SkillContext()

    # 创建临时目录
    self.temp_dir = tempfile.mkdtemp()

    # 创建测试文件
    self.test_file = os.path.join(self.temp_dir, "test.txt")
    with open(self.test_file, 'w') as f:
        f.write("Hello World\nThis is a test file\n")

def teardown_method(self):
    """清理方法"""
    # 删除临时目录
    import shutil
    shutil.rmtree(self.temp_dir)

def test_analyze_file(self):
    """测试文件分析"""
    result = self.skill.execute(
        {
            "path": self.test_file,
            "analysis_type": "all"
        },
        self.context
    )

    assert result.success
    assert result.data["type"] == "file"
    assert result.data["name"] == "test.txt"
    assert "size" in result.data
    assert "content" in result.data

def test_analyze_directory(self):
    """测试目录分析"""
    result = self.skill.execute(
bash
        {
            "path": self.temp_dir,
            "analysis_type": "structure"
        },
        self.context
    )

    assert result.success
    assert result.data["type"] == "directory"
    assert result.data["file_count"] == 1

def test_analyze_nonexistent_path(self):
    """测试不存在的路径"""
    result = self.skill.execute(
        {
            "path": "/nonexistent/path",
            "analysis_type": "all"
        },
        self.context
    )

    assert not result.success
    assert "error" in result.data

2.2 上下文集成测试

tests/test_context_integration.py

import pytest from claude_code_sdk import SkillContext class TestContextIntegration: """上下文集成测试""" def test_context_file_operations(self, tmp_path): """测试上下文文件操作""" context = SkillContext()

写入文件

test_file = tmp_path / "test.txt" context.write_file(str(test_file), "Hello World")

读取文件

content = context.read_file(str(test_file)) assert content == "Hello World"

检查文件存在

assert context.file_exists(str(test_file)) def test_context_search_operations(self, tmp_path): """测试上下文搜索操作""" context = SkillContext()

创建测试文件

test_file = tmp_path / "test.py" test_file.write_text("def test_function():\n pass\n")

搜索代码

results = context.search_codebase("test_function", str(tmp_path)) assert len(results) > 0 def test_context_command_operations(self): """测试上下文命令操作""" context = SkillContext()

执行命令

output = context.run_command("echo 'Hello'") assert "Hello" in output

bash
### 3. 端到端测试
#### 3.1 完整工作流测试

```python
# tests/test_e2e.py
import pytest
import os
import tempfile
from skills.code_generator import CodeGeneratorSkill
from skills.test_generator import TestGeneratorSkill
from claude_code_sdk import SkillContext

class TestEndToEnd:
    """端到端测试"""

    def setup_method(self):
        """设置方法"""
        self.context = SkillContext()
        self.temp_dir = tempfile.mkdtemp()

    def teardown_method(self):
        """清理方法"""
        import shutil
        shutil.rmtree(self.temp_dir)

    def test_code_generation_to_test_generation(self):
        """测试代码生成到测试生成的完整流程"""
        # 步骤 1:生成代码
        code_generator = CodeGeneratorSkill()
        code_result = code_generator.execute(
            {
                "language": "python",
                "type": "function",
                "name": "calculate_sum",
                "description": "Calculate sum of two numbers",
                "parameters": [
                    {"name": "a", "type": "int"},
                    {"name": "b", "type": "int"}
                ],
                "return_type": "int"
            },
            self.context
        )

        assert code_result.success

        # 步骤 2:保存生成的代码
        code_file = os.path.join(self.temp_dir, "utils.py")
        self.context.write_file(code_file, code_result.data["code"])

        # 步骤 3:生成测试
        test_generator = TestGeneratorSkill()
        test_result = test_generator.execute(
            {
                "file_path": code_file,
                "test_framework": "pytest"
            },
            self.context
        )

        assert test_result.success
        assert "test_code" in test_result.data

        # 步骤 4:保存测试代码
        test_file = os.path.join(self.temp_dir, "test_utils.py")
        self.context.write_file(test_file, test_result.data["test_code"])

        # 验证文件存在
        assert os.path.exists(code_file)
        assert os.path.exists(test_file)
```

## 调试技巧

### 1. 日志调试

#### 1.1 添加日志

```python
# src/skills/my_skill.py
import logging

class MySkill(Skill):
    def __init__(self):
        super().__init__(
            name="my-skill",
            version="1.0.0",
            description="A custom Claude Code skill"
        )
        # 设置日志
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.DEBUG)

    def execute(self, parameters, context):
        self.logger.debug("Starting execution")
        self.logger.debug(f"Parameters: {parameters}")
        try:
            # 处理逻辑
            result = self.process(parameters)
            self.logger.debug(f"Result: {result}")
            return result
        except Exception as e:
            self.logger.error(f"Error: {e}", exc_info=True)
            raise
```

#### 1.2 配置日志

```python
# src/skills/logger_config.py
import logging
import sys

def setup_logging(level=logging.DEBUG):
    """设置日志"""
    # 创建格式化器
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )

    # 创建控制台处理器
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(level)
    console_handler.setFormatter(formatter)

    # 配置根日志记录器
    root_logger = logging.getLogger()
    root_logger.setLevel(level)
    root_logger.addHandler(console_handler)

    # 配置文件处理器
    file_handler = logging.FileHandler('debug.log')
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    root_logger.addHandler(file_handler)

# 在 Skill 初始化时调用
setup_logging()
```

### 2. 断点调试

#### 2.1 使用 pdb

```python
# src/skills/my_skill.py
import pdb

class MySkill(Skill):
    def execute(self, parameters, context):
        # 设置断点
        pdb.set_trace()
        # 处理逻辑
        result = self.process(parameters)
        return result
```

#### 2.2 使用 ipdb

```python
# src/skills/my_skill.py
import ipdb

class MySkill(Skill):
    def execute(self, parameters, context):
        # 设置断点
        ipdb.set_trace()

        # 处理逻辑
        result = self.process(parameters)

        return result
```

#### 2.3 使用 VS Code 调试器

```json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Debug Skill",
            "type": "python",
            "request": "launch",
            "module": "pytest",
            "args": [
                "tests/test_my_skill.py::TestMySkill::test_execute"
            ],
            "console": "integratedTerminal",
            "env": {
                "PYTHONPATH": "${workspaceFolder}/src"
            }
        }
    ]
}
```

### 3. Mock 和 Stub
#### 3.1 Mock 上下文

```python
# tests/test_my_skill_mock.py
import pytest
from unittest.mock import Mock, MagicMock
from skills.my_skill import MySkill

class TestMySkillMock:
    """使用 Mock 的测试"""

    def test_execute_with_mock_context(self):
        """使用模拟上下文测试"""
        skill = MySkill()

        # 创建模拟上下文
        mock_context = Mock()
        mock_context.read_file.return_value = "file content"
        mock_context.write_file.return_value = None

        # 执行 Skill
        result = skill.execute(
            {"input": "test"},
            mock_context
        )

        # 验证结果
        assert result.success

        # 验证模拟对象被调用
        mock_context.read_file.assert_called_once()
```

#### 3.2 Mock 外部依赖

```python
# tests/test_my_skill_external.py
import pytest
from unittest.mock import patch
from skills.my_skill import MySkill

class TestMySkillExternal:
    """测试外部依赖"""
    @patch('skills.my_skill.external_api_call')
    def test_execute_with_external_api(self, mock_api):
        """使用外部 API 的测试"""
        skill = MySkill()
        # 设置模拟返回值
        mock_api.return_value = {"status": "success"}
        # 执行 Skill
        result = skill.execute({"input": "test"}, Mock())
        # 验证结果
        assert result.success
        # 验证 API 被调用
        mock_api.assert_called_once()
```

性能测试#

1. 基准测试#

python
# tests/test_performance.py import pytest import time from skills.text_processor import TextProcessorSkill from claude_code_sdk import SkillContext class TestPerformance: """性能测试""" def test_text_processing_performance(self): """测试文本处理性能""" skill = TextProcessorSkill() context = SkillContext() # 准备测试数据 large_text = "hello " * 10000 # 测量执行时间 start_time = time.time() result = skill.execute( { "text": large_text, "operations": [{"type": "uppercase"}] }, context ) end_time = time.time() # 验证结果 assert result.success # 验证性能(应该在 1 秒内完成) execution_time = end_time - start_time assert execution_time < 1.0, f"Execution took {execution_time} seconds" def test_file_analysis_performance(self, tmp_path): """测试文件分析性能""" from skills.file_analyzer import FileAnalyzerSkill skill = FileAnalyzerSkill() context = SkillContext() # 创建测试文件 test_file = tmp_path / "test.txt" test_file.write_text("x" * 1000000) # 测量执行时间 start_time = time.time() result = skill.execute( { "path": str(test_file), "analysis_type": "size" }, context ) end_time = time.time() # 验证结果 assert result.success # 验证性能 execution_time = end_time - start_time assert execution_time < 0.5, f"Execution took {execution_time} seconds"

2. 内存测试#

python
# tests/test_memory.py import pytest import tracemalloc from skills.text_processor import TextProcessorSkill from claude_code_sdk import SkillContext class TestMemory: """内存测试""" def test_memory_usage(self): """测试内存使用""" skill = TextProcessorSkill() context = SkillContext() # 开始内存跟踪 tracemalloc.start() # 执行操作 for i in range(100): skill.execute( { "text": "hello world " * 100, "operations": [{"type": "uppercase"}] }, context ) # 获取内存使用情况 current, peak = tracemalloc.get_traced_memory() # 停止内存跟踪 tracemalloc.stop() # 验证内存使用(峰值应该小于 10MB) peak_mb = peak / 1024 / 1024 assert peak_mb < 10, f"Peak memory usage: {peak_mb} MB"
bash
## 测试覆盖率
### 1. 生成覆盖率报告

```bash
# 运行测试并生成覆盖率报告
pytest --cov=src/skills --cov-report=html --cov-report=term

# 查看覆盖率报告
open htmlcov/index.html
```

### 2. 配置覆盖率

```ini
# .coveragerc
[run]
source = src/skills
omit =
    */tests/*
    */__pycache__/*
    */site-packages/*

[report]
exclude_lines =
    pragma: no cover
    def __repr__
    raise AssertionError
    raise NotImplementedError
    if __name__ == .__main__.:
    if TYPE_CHECKING:

[html]
directory = htmlcov
```

## 调试工具
### 1. 使用 pytest-debug

```bash
# 安装 pytest-debug
pip install pytest-debug

# 在测试失败时进入调试器
pytest --pdb
```

### 2. 使用 pytest-pdb

```bash
# 安装 pytest-pdb
pip install pytest-pdb

# 在测试失败时自动进入 pdb
pytest --pdb
```

### 3. 使用 pytest-sugar

```bash
# 安装 pytest-sugar
pip install pytest-sugar

# 使用更友好的输出
pytest -v
```

## 最佳实践

### 1. 测试编写原则

#### 1.1 独立性
- 每个测试应该独立运行
- 不依赖其他测试的状态
- 使用 setup 和 teardown 方法

#### 1.2 可重复性
- 测试应该可以重复运行
- 不依赖外部状态
- 使用固定的测试数据

#### 1.3 快速性
- 测试应该快速执行
- 避免不必要的等待
- 使用 Mock 隔离外部依赖

#### 1.4 可读性
- 测试名称应该清晰
- 使用描述性的断言
- 添加必要的注释

### 2. 调试技巧

#### 2.1 分而治之
- 将复杂问题分解为小问题
- 逐个测试每个组件
- 使用单元测试隔离问题

#### 2.2 添加日志
- 在关键位置添加日志
- 记录输入和输出
- 使用不同的日志级别

#### 2.3 使用断点
- 在可疑位置设置断点
- 检查变量值
- 单步执行代码

#### 2.4 使用 Mock
- Mock 外部依赖
- 控制测试环境
- 简化测试场景

## 总结

测试和调试是开发高质量 Skills 的关键环节。通过合理的测试策略、有效的调试技巧和完善的工具支持,可以显著提高 Skills 的质量和可靠性。

在下一章中,我们将探讨 Skills 的实际应用,展示如何在不同场景中使用 Skills。

标记本节教程为已读

记录您的学习进度,方便后续查看。